/***************************************************************************
 *   mgpi_shaders_test.c - Roeskva Shaders System Test                     *
 *   Created on 13 październik 2009, 16:33                                 *
 *   Copyright (C) 2009 by cmaster.matso (Maciej Kadłubowski)              *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <MGPI/mgpi.h>

/* Constants */
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 800

/* STATIC Global Variables */
static Roeskva_t engine;
static HANDLE hsbx, hhmp, hcamera;
static MGPI_Container *container;
static float speedf = 0.5f;

static GLuint light_effect[2];
static GLuint tex_effect[2];
static const char *vert_src_light, *frag_src_light;
static const char *vert_src_tex, *frag_src_tex;

/* STATIC Routines Declarations */
static MGPI_BOOL key_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL mouse_motion_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL mouse_button_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL _empty(MGPI_Component *, SDL_Event *);
static void skybox_trans(MGPI_RendNode *);
static void heightmap_trans(MGPI_RendNode *);
static void user_engine_setup(void);
static void lighting_on(MGPI_RendNode *);
static void lighting_off(MGPI_RendNode *);

/**
 * Initializes the Roeskva for work.
 */
static void init_engine(int *pargc, char *argv[]) {
    float4 position = {128., 200., 0., 1.};

    engImplement(&engine);
    if (!engStart(&engine)) exit(EXIT_FAILURE);

    hcamera = engine.createCamera(MGPI_PERSPECTIVE);

    engine.setProperty(MGPI_CAMERA_POSITION, hcamera, position);
    engine.setProperty(MGPI_CAMERA_NEAR, hcamera, 0.1f);
    engine.setProperty(MGPI_CAMERA_FAR, hcamera, 100000.0f);
    engine.setProperty(MGPI_CAMERA_FOVY, hcamera, 60.0f);
    engine.setProperty(MGPI_CAMERA_VIEWPORT, hcamera, SCREEN_WIDTH, SCREEN_HEIGHT);

    engine.setup(MGPI_SETUP_USER, user_engine_setup);

    engine.setProperty(MGPI_SHADERS);

    SDL_WM_SetCaption("Roeskva Test", NULL);
    SDL_ShowCursor(SDL_DISABLE);

    return;
}

/**
 * Sets up OpenGL parameters used in the engine.
 */
static void user_engine_setup(void) {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glShadeModel(GL_SMOOTH);

    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_MULTISAMPLE_ARB);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    return;
}

/**
 * Initializes the scene.
 */
static void init_scene(void) {
    int tex;

    gsubSetTexCoordsWrapping(500.0f, 500.f);

    /* Put your paths... */
    if ((hsbx = engine.loadObject(NULL, MGPI_SKYBOX)) < 0) exit(EXIT_FAILURE);
    if ((hhmp = engine.loadObject("../../../../../data/textures/terrain.bmp", MGPI_HEIGHTMAP)) < 0) exit(EXIT_FAILURE);

    if (!(tex = g3dTexture2DType("../../../../../data/textures/rockbump.jpg", GL_RGB))) exit(EXIT_FAILURE);

    engine.selectObject(hhmp);
    engine.setProperty(MGPI_TEXTURE, tex, GL_TEXTURE_2D);

    if (!(tex = g3dTexture2DType("../../../../../data/textures/skybox0.jpg", GL_RGB))) exit(EXIT_FAILURE);

    engine.selectObject(hsbx);
    engine.setProperty(MGPI_TEXTURE, tex, GL_TEXTURE_2D);
    /*if (!engine.loadTexture(hhmp, GL_RGB, "../../../../../data/textures/rockbump.jpg")) exit(EXIT_FAILURE);*/

    if (!(vert_src_light = (const char *) fload("../../../../../data/shaders/light.vert"))) exit(EXIT_FAILURE);
    /*if (!(vert_src_light = (const char *) fload("../../../../../data/shaders/vertexcolor.vert"))) exit(EXIT_FAILURE);*/

    if (!(vert_src_tex = (const char *) fload("../../../../../data/shaders/simple.vert"))) exit(EXIT_FAILURE);

    if (!(frag_src_light = (const char *) fload("../../../../../data/shaders/light.frag"))) exit(EXIT_FAILURE);
    /*if (!(frag_src_light = (const char *) fload("../../../../../data/shaders/vertexcolor.frag"))) exit(EXIT_FAILURE);*/
    
    if (!(frag_src_tex = (const char *) fload("../../../../../data/shaders/simple.frag"))) exit(EXIT_FAILURE);

    engine.setObjectTransformations(hsbx, skybox_trans);
    engine.setObjectTransformations(hhmp, heightmap_trans);

    /* Stuff essential for doing the shaders */
    light_effect[0] = engine.setupShader(MGPI_VERTEX_SHADER, vert_src_light);
    light_effect[1] = engine.setupShader(MGPI_FRAGMENT_SHADER, frag_src_light);

    tex_effect[0] = engine.setupShader(MGPI_VERTEX_SHADER, vert_src_tex);
    tex_effect[1] = engine.setupShader(MGPI_FRAGMENT_SHADER, frag_src_tex);

    engine.selectObject(hsbx);
    engine.setProperty(MGPI_OBJECT_EFFECT, 2, tex_effect);

    engine.selectObject(hhmp);
    engine.setProperty(MGPI_OBJECT_EFFECT, 2, light_effect);
    
    engine.setObjectGLCommands(hsbx, MGPI_GL_CMD_COMBINED, 2,
            MGPI_GL_CMD_TEXTURE,
            MGPI_GL_CMD_SHADERS);

    engine.setObjectGLCommands(hhmp, MGPI_GL_CMD_COMBINED, 3,
            MGPI_GL_CMD_TEXTURE,
            MGPI_GL_CMD_USER, lighting_on, lighting_off,
            MGPI_GL_CMD_SHADERS);

    return;
}

/**
 * Initializes the scene container (for logic processing purposes).
 */
static void init_container(void) {
    container = (MGPI_Container *) calloc(1, sizeof (MGPI_Container));
    MGPI_CollectGarbage(container);

    container->component.onEvent[MGPI_MOUSE_BUTTON_DOWN] = mouse_button_proc;
    container->component.onEvent[MGPI_MOUSE_BUTTON_UP] = _empty;
    container->component.onEvent[MGPI_MOUSE_MOTION] = mouse_motion_proc;
    container->component.onEvent[MGPI_KEY_DOWN] = key_proc;
    container->component.onEvent[MGPI_KEY_UP] = _empty;
    return;
}

/**
 * Renders the scene.
 */
static void render_scene(void) {
    engine.updateCamera(hcamera);
    engine.renderDiagnostic();

    engine.waitFrame(60);

    engine.renderObject(hsbx);
    engine.renderObject(hhmp);

    g3dDrawCoordinates();

    engine.commitChanges();

    SDL_Delay(12);

    return;
}

/**
 * Routine called at program exit.
 */
static void at_exit(void) {
    free((char *) frag_src_light);
    free((char *) frag_src_tex);
    free((char *) vert_src_light);
    free((char *) vert_src_tex);

    engStop(&engine);
    MGPI_Exit();
    return;
}

/**
 * Transforms the skybox.
 */
static void skybox_trans(MGPI_RendNode *node) {
    float4 vect = MGPI_DEFAULT_VECTOR;
    g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_POSITION);

    glTranslatef(vect[X], vect[Y], vect[Z]);
    glScalef(1000.0f, 1000.0f, 1000.0f);
    return;
}

/**
 * Transforms the height map.
 */
static void heightmap_trans(MGPI_RendNode *node) {
    glTranslatef(0.0f, -10.0f, 0.0f);
    glScalef(1.0f, 0.1f, 1.0f);
    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_on(MGPI_RendNode *node) {
    /*static float angle = 0.0f;
    float4 axis = {0.0f, 1.0f, 0.0f, 1.0f};
    float step = (float) ((0.8f * MGPI_PI) / 180.0f);*/

    float4 light_pos0 = {0.0f, 0.0f, 0.0f, 1.0f};
    float4 light_pos1 = {0.0f, 0.0f, 0.0f, 1.0f};
    float4 light_pos2 = {0.0f, 0.0f, 0.0f, 1.0f};

    float4 light_red = {1.0f, 0.0f, 0.0f, 1.0f};
    float4 light_green = {0.0f, 1.0f, 0.0f, 1.0f};
    float4 light_blue = {0.0f, 0.0f, 1.0f, 1.0f};

    float4 light_spec = {1.0f, 0.0f, 0.0f, 1.0f};
    float4 eye_pos = MGPI_DEFAULT_VECTOR;
    
    g3dLoadIdentity();
    g3dTranslatef(128.0f, 30.0f, -128.0f);
    /*g3dRotatef(angle, axis);*/
    g3dTranslatef(0.0f, 0.0f, -50.0f);
    g3dInputv(light_pos0);
    g3dTransform(&light_pos0);

    g3dLoadIdentity();
    g3dTranslatef(128.0f, 30.0f, -128.0f);
    /*g3dRotatef(angle, axis);*/
    g3dTranslatef(50.0f, 0.0f, 0.0f);
    g3dInputv(light_pos1);
    g3dTransform(&light_pos1);

    g3dLoadIdentity();
    g3dTranslatef(128.0f, 30.0f, -128.0f);
    /*g3dRotatef(angle, axis);*/
    g3dTranslatef(0.0f, 0.0f, 50.0f);
    g3dInputv(light_pos2);
    g3dTransform(&light_pos2);

    /*angle += step;
    if (angle == (2 * MGPI_PI)) angle = 0.0f;*/

    engine.setProperty(MGPI_LIGHTS);
    engine.setProperty(MGPI_GL_ON, GL_LIGHT0);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT0, light_pos0[0], light_pos0[1], light_pos0[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT0, light_red[0], light_red[1], light_red[2]);
    engine.setProperty(MGPI_LIGHT_SPECULAR, GL_LIGHT0, light_spec[0], light_spec[0], light_spec[1]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT0, 0.0f, 0.0f, 0.005f);

    engine.setProperty(MGPI_GL_ON, GL_LIGHT1);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT1, light_pos1[0], light_pos1[1], light_pos1[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT1, light_green[0], light_green[1], light_green[2]);
    engine.setProperty(MGPI_LIGHT_SPECULAR, GL_LIGHT1, light_spec[1], light_spec[0], light_spec[0]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT1, 0.0f, 0.0f, 0.005f);

    engine.setProperty(MGPI_GL_ON, GL_LIGHT2);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT2, light_pos2[0], light_pos2[1], light_pos2[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT2, light_blue[0], light_blue[1], light_blue[2]);
    engine.setProperty(MGPI_LIGHT_SPECULAR, GL_LIGHT2, light_spec[0], light_spec[1], light_spec[0]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT2, 0.0f, 0.0f, 0.005f);

    engine.setProperty(MGPI_MATERIAL_SPECULAR, GL_FRONT, 1.0f, 1.0f, 1.0f);
    engine.setProperty(MGPI_MATERIAL_SHININESS, GL_FRONT, 700.0f);

    /* Example for passing uniform variable to GL */
    g3dGetCamVect(&eye_pos, hcamera, MGPI_CAMERA_POSITION);

    engine.selectProgram(hhmp);
    engine.setVariable(MGPI_GLSL_FLOAT4, MGPI_GLSL_UNIFORM, "EyePosition", eye_pos[0], eye_pos[1], eye_pos[2], eye_pos[3]);

    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_off(MGPI_RendNode *node) {

    engine.setProperty(MGPI_GL_OFF, GL_LIGHT0);
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT1);
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT2);
    engine.setProperty(MGPI_LIGHTS);
    return;
}

/**
 * Processes key events.
 */
static MGPI_BOOL key_proc(MGPI_Component *component, SDL_Event *event) {
    float4 vect = MGPI_DEFAULT_VECTOR;
    int num;
    Uint8 *keys = SDL_GetKeyState(&num);

    if(keys[SDLK_ESCAPE]) return MGPI_FALSE;

    if (keys[SDLK_SPACE]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_UP);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

    if (keys[SDLK_LCTRL]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_UP);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_q]) engine.setProperty(MGPI_TOGGLE_FULL_SCREEN);

    if (keys[SDLK_w]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_FORWARD);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

    if (keys[SDLK_s]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_FORWARD);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_a]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_RIGHT);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_d]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_RIGHT);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

    return MGPI_TRUE;
}

/**
 * Processes mouse motion events.
 */
static MGPI_BOOL mouse_motion_proc(MGPI_Component *component, SDL_Event *event) {
    static int last_x = SCREEN_WIDTH / 2;
    static int last_y = SCREEN_HEIGHT / 2;
    int current_x = event->motion.x;
    int current_y = event->motion.y;
    int dx = last_x - current_x;
    int dy = last_y - current_y;
    float4 right = MGPI_DEFAULT_VECTOR, up = {0.0f, 1.0f, 0.0f, 1.0f};

    g3dGetCamVect(&right, hcamera, MGPI_CAMERA_RIGHT);

    g3dRotateCamf(hcamera, (float) ((dy * MGPI_PI) / 180.0f), right);
    g3dRotateCamf(hcamera, (float) ((dx * MGPI_PI) / 180.0f), up);

    last_x = current_x;
    last_y = current_y;
    return MGPI_TRUE;
}

/**
 * Processes mouse buttons events.
 */
static MGPI_BOOL mouse_button_proc(MGPI_Component *component, SDL_Event *event) {
    switch (event->button.button) {
        case SDL_BUTTON_WHEELUP:
            g3dZoomIn(hcamera);
            break;

        case SDL_BUTTON_WHEELDOWN:
            g3dZoomOut(hcamera);
            break;

        default: break;
    }
    return MGPI_TRUE;
}

/**
 * Does nothing.
 */
static MGPI_BOOL _empty(MGPI_Component *component, SDL_Event *event) {
    return MGPI_TRUE;
}

/**
 * Program entry point.
 */
int main(int argc, char *argv[]) {

    MGPI_Init();
    atexit(at_exit);

    glutInit(&argc, argv);
    init_engine(&argc, argv);
    init_container();
    init_scene();

    MGPI_InitEvents(MGPI_THREAD_EVENTS, container);
    while (MGPI_STATE) render_scene();

    return 0;
}
